/*********************************************************************
**	File: jsl.hpp
**	Author: Asteroth
**	Date: 05/09/2010
**  Comment: Yeah, yeah, I know that recursive function cannot be
**      inline, that is just for esthetic reasons.
*********************************************************************/

/*********************************************************************
// In using this lib, you should follow a few simple rules to avoid
// memory leaks and segmentation faults:
// 1. You must delete nodes returned by parse methods like parseFile,
//    parseJslFile, parseBinaryFile, parseJslStream and parseBinaryStream.
// 2. You must delete nodes and objects returned by detach methods like
//    detachField, detachObject and detachArray.
// 3. You mustn't delete any other nodes or objects returned by any other
//    methods.


// Basic example, opening and saving jsl files -----------------------

    jsl::Node* node;
    node = jsl::Node::parseFile("example.jsl"); // parses a jsl file format determining it's format from the extension.
    if (node != NULL) {
        node->saveFile("example2.jsl");             // saves into a file with jsl format.
        node->saveFile("example2.bin");             // saves into a file with binary jsl format.
        node->saveJslFile("example2.bin");          // saves into a file with jsl format, regardles the extension.
        node->saveBinaryFile("example2.jsl");       // saves into a file with binary jsl format, regardles the extension.
        delete node;                                // always delete nodes returned by parse methods.
    }

// Basic example, access values --------------------------------------

    jsl::Node* node = NULL;             // always initialize nodes, even if it's to null.
    cout << node->type();               // simple getting values won't cause errors when node is null.
    cout << node->asInt();
    cout << node->isNull();
    cout << node->val();
    cout << node->at("myField")->val();
    delete node;                        // deleting null nodes won't cause problems.

    node = jsl::Node::parseFile("example.jsl"); // usually parsed nodes are of type ObjectType.
    cout << node->at("myField")->val();         // prints the string representing the value of the field "myField",
                                                // empty string if the field does not exists.
    cout << node->at("myInt")->asInt();         // prints the integer value if the field "myInt" is a valid
                                                // integer field or something convertible to int, will print 0
                                                // if the field does not exists, throws exception if the field
                                                // exists but is not an integer or convertible to integer.
                                                // all this rules applies to other "as" methods like asDouble and asBool.
    jsl::Node* array = node->at("myArray");     // don't delete this node, its not returned by a parse method, and is not detached.
    for (unsigned int i = array->size(); $i++)  // this usage is safe, even if the "array" node is not really an array,
                                                // cause the size method would returns 0 in that case.
        cout << array->at(i)->at("myField")->val();
    delete node;

// Advanced example --------------------------------------------------
    jsl::Node* node = jsl::Node::parseFile("example.jsl");
    jsl::Node* needed = NULL;
    if (node != NULL) {
        cout << node->path("myData.myArray[23].myObject.myField")->val(); // this prints the value of "myField"
        jsl::Node* tmp = node->path("myData.myArray[23].myObject");
        needed = tmp->detachField("myField"); // you cant access "myField" from main node now, but you have it in "needed" node.
        delete node; // this won't delete the detached nodes
    }
    cout << needed->val(); // this also prints the value of "myField", even if the main node is deleted
    delete needed; // always delete detached nodes

*********************************************************************/

#ifndef __JSL_HPP__
#define __JSL_HPP__

#include <stdio.h>
#include <fstream>
#include <map>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <string_functions.hpp>
#include <boost/lexical_cast.hpp>

namespace jsl {

#define JSLbool     bool
#define JSLchar     char
#define JSLbyte     char
#define JSLubyte    unsigned char
#define JSLshort    short
#define JSLushort  unsigned short
#define JSLint      int
#define JSLuint     unsigned int
#define JSLlong     long long
#define JSLulong    unsigned long long
#define JSLsingle   float
#define JSLdouble   double

//#define setmem(value, pointer)    (*((typeof(value)*)(pointer)) = value)
#define setmem(type, pointer, value)    (*((type*)(pointer)) = value)
#define getmem(type, pointer)           (*((type*)(pointer)))
#define nextNonWhite(outPos, stream, position, length) {outPos = (position); while (outPos < (length) && isspace(stream[outPos])) outPos++;}

const JSLchar DEFAULT_STRING_DELIMITER = '"';

const JSLubyte NullType = 0;
const JSLubyte BoolType = 1;
const JSLubyte IntType = 2;
const JSLubyte LongType = 3;
const JSLubyte FloatType = 4;
const JSLubyte DoubleType = 5;
const JSLubyte StringType = 6;
const JSLubyte ObjectType = 7;
const JSLubyte ArrayType = 8;

class Node;
typedef std::string JSLstring;
typedef std::string::size_type JSLstringSize;
typedef std::map<JSLstring, Node*> JSLobject;
typedef std::map<JSLstring, Node*>::size_type JSLobjectSize;
typedef std::map<JSLstring, Node*>::iterator JSLobjectIterator;
typedef std::vector<Node*> JSLarray;
typedef std::vector<Node*>::size_type JSLarraySize;

union Data {
    JSLbool asBool;
    JSLint asInt;
    JSLlong asLong;
    JSLsingle asFloat;
    JSLdouble asDouble;
    JSLstring* asString;
    JSLobject* asObject;
    JSLarray* asArray;
};

class Node {
protected:
    JSLubyte _type;
    Data _data;

public:
    Node(): _type(NullType) {};
    ~Node() {free();}

    inline JSLubyte		type()      const {if (this == NULL) return NullType; return _type;}
    inline JSLbool		isNull()    const {if (this == NULL) return true; return _type == NullType;};
    inline JSLbool		asBool()    const {if (this == NULL) return false; return _type == BoolType ? _data.asBool : (_type == LongType ? _data.asLong != 0 : (_type == IntType ? _data.asInt != 0 : (_type == DoubleType ? _data.asDouble != 0 : (_type == FloatType ? _data.asFloat != 0 : false))));};
    inline JSLint		asInt()     const {if (this == NULL) return 0; return _type == IntType ? _data.asInt : (_type == LongType ? (JSLint)_data.asLong : (_type == DoubleType ? (JSLint)_data.asDouble : (_type == FloatType ? (JSLint)_data.asFloat : (_type == BoolType ? (_data.asBool ? 1 : 0) : 0))));};
    inline JSLlong		asLong()    const {if (this == NULL) return 0; return _type == LongType ? _data.asLong : (_type == IntType ? _data.asInt : (_type == DoubleType ? (JSLint)_data.asDouble : (_type == FloatType ? (JSLint)_data.asFloat : (_type == BoolType ? (_data.asBool ? 1 : 0) : 0))));};
    inline JSLsingle	asFloat()   const {if (this == NULL) return 0; return _type == FloatType ? _data.asFloat : (_type == DoubleType ? (JSLsingle)_data.asDouble : (_type == LongType ? _data.asLong : (_type == IntType ? _data.asInt : (_type == BoolType ? (_data.asBool ? 1.0f : 0.0f) : 0.0f))));};
    inline JSLdouble	asDouble()  const {if (this == NULL) return 0; return _type == DoubleType ? _data.asDouble : (_type == FloatType ? _data.asFloat : (_type == LongType ? _data.asLong : (_type == IntType ? _data.asInt : (_type == BoolType ? (_data.asBool ? 1.0 : 0.0) : 0.0))));};
    inline JSLstring	asString()  const {if (this == NULL) return ""; return _type == StringType ? *_data.asString : (_type == DoubleType ? boost::lexical_cast<std::string>(_data.asDouble) : (_type == FloatType ? boost::lexical_cast<std::string>(_data.asFloat) : (_type == LongType ? boost::lexical_cast<std::string>(_data.asLong) : (_type == IntType ? boost::lexical_cast<std::string>(_data.asInt) : (_type == BoolType ? (_data.asBool ? "true" : "false") : (_type == NullType ? "" : this->toString()))))));};
    inline JSLobject*	asObject()  const {if (this == NULL) return NULL; return _type == ObjectType ? _data.asObject : NULL;};
    inline JSLarray*	asArray()   const {if (this == NULL) return NULL; return _type == ArrayType ? _data.asArray : NULL;};

    /**
     * If the node is an array then returns the node at the provided index of the array, null otherwise.
     */
	inline Node* at(JSLarraySize index) const {if (this == NULL) return NULL; return _type == ArrayType && index < _data.asArray->size() ? (*_data.asArray)[index] : NULL;}

    /**
     * If the node is an object then returns the node at the provided field of the object, null otherwise.
     */
    inline Node* at(JSLstring name) const {if (this == NULL) return NULL; JSLobjectIterator iter; return _type == ObjectType && (iter = _data.asObject->find(name)) != _data.asObject->end() ? iter->second : NULL;}

    /**
     * If the node is an array then returns the size, zero otherwise.
     */
    inline JSLarraySize size() const {return (this != NULL && _type == ArrayType) ? _data.asArray->size() : 0;}

    /**
     * Gets the string value of the node.
     */
    inline JSLstring value() {
        return this->asString();
    }

    /**
     * Gets the string value of the node on the path.
     */
    inline JSLstring get(const JSLstring &path) {
        Node* node = this->path(path, false);
        if (node == NULL)
            return "";
        return node->value();
    }

    inline void setNull() {
        free();
    }

    inline void set(JSLbool value) {
        free();
        _type = BoolType;
        _data.asBool = value;
    }

    inline void set(JSLint value) {
        free();
        _type = IntType;
        _data.asInt = value;
    }

    inline void set(const JSLlong &value) {
        free();
        _type = LongType;
        _data.asLong = value;
    }

    inline void set(JSLsingle value) {
        free();
        _type = FloatType;
        _data.asFloat = value;
    }

    inline void set(const JSLdouble &value) {
        free();
        _type = DoubleType;
        _data.asDouble = value;
    }

    /**
     * Copies the value and sets it to the node.
     */
    inline void set(const JSLstring &value) {
        free();
        _type = StringType;
        _data.asString = new JSLstring(value);
    }

	/**
     * Sets the value of the node, the passed value must not be deallocated or modified after this,
     * passing an inline variable would cause an error later.
     */
    inline void setThis(JSLstring* value) {
        free();
        _type = StringType;
        _data.asString = value;
    }

    inline JSLbool set(const JSLstring &path, JSLbool value) {
		Node* node = this->path(path, true);
        if (node == NULL)
            return false;
        node->set(value);
        return true;
	}

    inline JSLbool set(const JSLstring &path, JSLint value) {
		Node* node = this->path(path, true);
        if (node == NULL)
            return false;
        node->set(value);
        return true;
	}

    inline JSLbool set(const JSLstring &path, const JSLlong &value) {
		Node* node = this->path(path, true);
        if (node == NULL)
            return false;
        node->set(value);
        return true;
	}

    inline JSLbool set(const JSLstring &path, JSLsingle value) {
		Node* node = this->path(path, true);
        if (node == NULL)
            return false;
        node->set(value);
        return true;
	}

    inline JSLbool set(const JSLstring &path, const JSLdouble &value) {
		Node* node = this->path(path, true);
        if (node == NULL)
            return false;
        node->set(value);
        return true;
	}

    /**
     * Copies the value and sets it to the node on the path.
     */
    inline JSLbool set(const JSLstring &path, const JSLstring &value) {
        Node* node = this->path(path, true);
        if (node == NULL)
            return false;
        node->set(value);
        return true;
    }

	/**
     * Sets the value of the node on the path, the passed value must not be deallocated or modified after this,
     * passing an inline variable would cause an error later.
     */
    inline JSLbool setThis(const JSLstring &path, JSLstring *value) {
		Node* node = this->path(path, true);
        if (node == NULL)
            return false;
        node->setThis(value);
        return true;
	}

    /**
     * Works with object type only, removes a field and returns it's value, so you can delete the object, and keep the value of the field.
     */
	inline Node* detachField(const JSLstring &name) {
        JSLobjectIterator iter;
        Node* field = _type == ObjectType && (iter = _data.asObject->find(name)) != _data.asObject->end() ? iter->second : NULL;
        if (field != NULL)
            _data.asObject->erase(iter);
        return field;
	}

    /**
     * Works with object type only, sets this node to null and returns it's object value, so you can delete the node after that and keep the object.
     */
	inline JSLobject* detachObject() {
        if (_type == ObjectType) {
        	JSLobject* tmp = _data.asObject;
            _data.asObject = NULL;
            _type = NullType;
            return tmp;
        }
        return NULL;
	}

    /**
     * Works with array type only, sets this node to null and returns it's array value, so you can delete the node after that and keep the array.
     */
	inline JSLarray* detachArray() {
        if (_type == ArrayType) {
        	JSLarray* tmp = _data.asArray;
            _data.asArray = NULL;
            _type = NullType;
            return tmp;
        }
        return NULL;
	}

    /**
     * Retrurns a string representing the value.
     */
    inline JSLstring toString() const {
        JSLstring result;
        JSLchar* stream;
        JSLstringSize length;
        switch (_type) {
            case StringType:
                return DEFAULT_STRING_DELIMITER + (*_data.asString) + DEFAULT_STRING_DELIMITER;
            case ObjectType:
            case ArrayType:
                length = this->asStringLength();
                stream = new JSLchar[length];
                length = this->writeJslStream(stream, 0, length);
                result.assign(stream, length);
                delete[] stream;
                return result;
            default:
                return this->asString();
        }
    }

    /**
     * Deletes all the content of the node and sets the value to null.
     */
    inline void free() {
		if (_type == StringType)
			delete _data.asString;
		else if (_type == ObjectType) {
			JSLobject* obj = _data.asObject;
			for (JSLobjectIterator iter = obj->begin(); iter != obj->end(); iter++)
				delete iter->second;
			delete obj;
		} else if (_type == ArrayType) {
			JSLarray* arr = _data.asArray;
			JSLstringSize size = arr->size();
			for (JSLstringSize i = 0; i < size; i++)
				delete (*arr)[i];
			delete arr;
		}
		_type = NullType;
	}

    /**
     * Navigates through the given path and returns it's target.
     */
    inline Node* path(const JSLstring &path) {
    	return this->path(path, false);
    }

    /**
     * Navigates through the given path and returns it's target, creates the path if not exists and create is set to true.
     */
    inline Node* path(const JSLstring &path, bool create) {
    	JSLstring first, rest;
    	if (extractFirstPath(path, first, rest)) {
    	    if (first == "")
                return this;
    		JSLstringSize firsti;
    		if (std::is_int(first))
                firsti = boost::lexical_cast<JSLstringSize>(first);
    		else
    			firsti = -1;
    		Node* result = NULL;
    		if (this->_type == ObjectType) {
    			result = this->at(first);
    			if (result == NULL) {
					if (create == false)
						return NULL;
					result = new Node();
					result->_type = NullType;
					JSLobjectIterator iter = _data.asObject->find(first);
					if (iter != _data.asObject->end()) {
                        delete iter->second;
                        _data.asObject->erase(iter);
                    }
                    this->_data.asObject->insert(std::pair<JSLstring, Node*>(first, result));
				}
            } else if (this->_type == ArrayType && firsti >= 0) {
    		    result = this->at(firsti);
    		    if (result == NULL) {
					if (create == false)
						return NULL;
					result = new Node();
					result->_type = NullType;
					if ((int)_data.asArray->size() > firsti) {
					    delete (*(_data.asArray))[firsti];
						(*(_data.asArray))[firsti] = result;
					} else
						_data.asArray->push_back(result);
				}
    		} else {
                if (create == false)
                    return NULL;
                result = new Node();
                result->_type = NullType;
                if (firsti >= 0) {
                    _type = ArrayType;
                    _data.asArray = new JSLarray();
                    _data.asArray->push_back(result);
                } else {
                    _type = ObjectType;
                    _data.asObject = new JSLobject();
                    (*(_data.asObject))[first] = (result);
                }
    		}
			if (rest != "")
				return result->path(rest, create);
			else
				return result;
    	} else
    		return NULL;
    }

    /**
     * Parses a file, determines its format form the file extension.
     */
    inline static Node* parseFile(const JSLstring &fileName) {
        if (std::ends_with(fileName, ".bin"))
            return parseBinaryFile(fileName);
        else
            return parseJslFile(fileName);
    }

    /**
     * Saves the contents into a file, determines its format form the file extension.
     */
    inline bool saveFile(const JSLstring &fileName) {
        if (std::ends_with(fileName, ".bin"))
            return saveBinaryFile(fileName);
        else
            return saveJslFile(fileName);
    }

    /**
     * Parses a jsl file.
     */
    inline static Node* parseJslFile(const JSLstring &fileName) {
        FILE* file = NULL;
		errno_t error = fopen_s(&file, fileName.c_str(), "rb");
        if (error != 0 || file == NULL)
            return NULL;

        fseek(file, 0, SEEK_END);
        JSLstringSize length = ftell(file);

        JSLchar* stream = new JSLchar[length + 1];
        if (stream == NULL) {
            fclose(file);
            return NULL;
        }

        stream[length] = 0;
        fseek(file, 0, SEEK_SET);
        size_t rsize = fread(stream, 1, length, file);
        fclose(file);
        if (rsize != length) {
            delete[] stream;
            return NULL;
        }

        removeComments(stream, length);

        Node* node = parseJslStream(stream, length);
        delete[] stream;
        return node;
    }

    /**
     * Parses a jsl stream.
     */
    inline static Node* parseJslStream(const JSLchar* stream, const JSLstringSize length) {
    	JSLint tmp;
        return parseObject(tmp, stream, 0, (JSLint)length);
    }

    /**
     * Parses a jsl binary file.
     */
    inline static Node* parseBinaryFile(const JSLstring &fileName) {
		FILE* file = NULL;
		errno_t error = fopen_s(&file, fileName.c_str(), "rb");
        if (error != 0 || file == NULL)
            return NULL;

        fseek(file, 0, SEEK_END);
        JSLstringSize length = ftell(file);

        JSLchar* stream = new JSLchar[length];
        if (stream == NULL) {
            fclose(file);
            return NULL;
        }

        fseek(file, 0, SEEK_SET);
        size_t rsize = fread(stream, 1, length, file);
        fclose(file);
        if (rsize != length) {
            delete[] stream;
            return NULL;
        }

        JSLstringSize pos = 0;
        Node* node = parseBinaryStream(stream, pos);
        delete[] stream;
        return node;
    }

    /**
     * Parses a jsl binary stream.
     */
    inline static Node* parseBinaryStream(const JSLchar* stream, JSLstringSize &pos) {
        JSLuint size;
        Node* result = new Node();
        result->_type = getmem(JSLubyte, stream + pos);
        pos += 1;
        switch (result->_type) {
            case NullType:
                break;
            case BoolType:
                result->_data.asBool = getmem(JSLbool, stream + pos);
                pos += 1;
                break;
            case LongType:
                result->_data.asLong = getmem(JSLlong, stream + pos);
                pos += 8;
                break;
            case DoubleType:
                result->_data.asDouble = getmem(JSLdouble, stream + pos);
                pos += 8;
                break;
            case IntType:
                result->_data.asInt = getmem(JSLint, stream + pos);
                pos += 4;
                break;
            case FloatType:
                result->_data.asFloat = getmem(JSLsingle, stream + pos);
                pos += 4;
                break;
            case StringType:
                result->_data.asString = new JSLstring(stream + getmem(JSLuint, stream + pos), getmem(JSLuint, stream + pos + 4));
                pos += 8;
                break;
            case ObjectType:
                size = getmem(JSLuint, stream + pos);
                pos += 4;
                result->_data.asObject = new JSLobject();
                for (JSLuint i = 0; i < size; i++) {
                    JSLstring field(stream + getmem(JSLuint, stream + pos), (JSLuint)getmem(JSLchar, stream + pos + 4));
                    pos += 5;
                    Node* value = parseBinaryStream(stream, pos);
                    if (value == NULL) {
                        delete result;
                        return NULL;
                    }
                    result->_data.asObject->insert(std::pair<JSLstring, Node*>(field, value));
                }
                break;
            case ArrayType:
                size = getmem(JSLuint, stream + pos);
                pos += 4;
                result->_data.asArray = new JSLarray();
                for (JSLuint i = 0; i < size; i++) {
                    Node* value = parseBinaryStream(stream, pos);
                    if (value == NULL) {
                        delete result;
                        return NULL;
                    }
                    result->_data.asArray->push_back(value);
                }
                break;
        }
        return result;
    }

    /**
     * Saves into a jsl binary file.
     */
    inline JSLbool saveBinaryFile(const JSLstring &fileName) {
        JSLchar* stream;
        JSLstringSize length;
        if (writeBinaryStream(stream, length)) {
			FILE* file = NULL;
			errno_t error = fopen_s(&file, fileName.c_str(), "wb");
            if (error != 0 || file == NULL)
                return false;
            JSLbool success = fwrite(stream, 1, length, file) == length;
            success = (fclose(file) == 0) && success;
            delete[] stream;
            return success;
        }
        return false;
    };

    /**
     * Saves into a jsl file.
     */
    inline JSLbool saveJslFile(const JSLstring &fileName) {
        std::ofstream file;
        file.open(fileName.c_str());
        if (file.is_open()) {
            print(file);
            file.close();
            return true;
        }
        return false;
    };

    /**
     * Prints the content into a stream, the same way it would appear in a jsl file.
     */
    inline void print(std::ostream &out, int depth = 0) {
        switch (_type) {
            case NullType:
                out <<"null";
                break;
            case BoolType:
                out <<(_data.asBool ? "true" : "false");
                break;
            case IntType:
                out <<boost::lexical_cast<JSLstring>(_data.asInt);
                break;
            case LongType:
                out <<boost::lexical_cast<JSLstring>(_data.asLong) <<"L";
                break;
            case FloatType:
                out <<boost::lexical_cast<JSLstring>(_data.asFloat);
                break;
            case DoubleType:
                out <<boost::lexical_cast<JSLstring>(_data.asDouble) <<"D";
                break;
            case StringType:
                out <<DEFAULT_STRING_DELIMITER <<*_data.asString <<DEFAULT_STRING_DELIMITER;
                break;
            case ObjectType:
                if (_data.asObject != NULL && _data.asObject->size() > 0) {
                    if (depth > 0) out <<"{\n";
                    JSLuint j = 1;
                    for (JSLobjectIterator iter = _data.asObject->begin(); iter != _data.asObject->end(); iter++) {
                        for (int i = 1; i <= depth; i++)
                            out <<"\t";
                        out <<iter->first <<": ";
                        iter->second->print(out, depth + 1);
                        if (j != _data.asObject->size())
                            out <<",";
                        out <<"\n";
                        j++;
                    }
                    for (int i = 1; i < depth; i++)
                        out <<"\t";
                    if (depth > 0) out <<"}";
                } else {
                    if (depth > 0) out <<"{}";
                }
                break;
            case ArrayType:
                if (_data.asArray != NULL && _data.asArray->size() > 0) {
                	JSLarray* arr = _data.asArray;
                    JSLarraySize size = arr->size();
                    out <<"[\n";
                    for (unsigned int i = 0; i < size; i++) {
                        for (int j = 1; j <= depth; j++)
                            out <<"\t";
                        (*arr)[i]->print(out, depth + 1);
                        if (i < size - 1)
                            out <<",";
                        out <<"\n";
                    }
                    for (int i = 1; i < depth; i++)
                        out <<"\t";
                    out <<"]";
                } else {
                    out <<"[]";
                }
                break;
        }
    }

    /**
     * Writes binary jsl content into a buffer stream.
     */
    inline JSLbool writeBinaryStream(JSLchar* &stream, JSLstringSize &length) {
    	JSLstringSize strucutreSize = 0;
    	JSLstringSize stringsSize = 0;
    	JSLstringSize totalSize = calcBinarySize(strucutreSize, stringsSize);
        stream = new JSLchar[totalSize];
        if (stream == NULL)
            return false;
        JSLstringSize strucutrePos = 0;
        JSLstringSize stringsPos = 0;
        if (writeBinaryStream(stream, stream + strucutreSize, strucutrePos, stringsPos, strucutreSize)) {
            length = totalSize;
            return true;
        }
        return false;
    }

private:
    inline static void removeComments(JSLchar* stream, const JSLstringSize length) {
    	JSLuint status = 0;
        for (JSLuint i = 0; i < length; i++)
            switch (status) {
                case 0:
                    if (stream[i] == '/')
						status = 1;
                    break;
                case 1:
                    if (stream[i] == '/')
						status = 2;
                    else if (stream[i] == '*')
						status = 3;
                    else {
                        status = 0;
                        break;
                    }
                    stream[i - 1] = ' ';
                    stream[i] = ' ';
                    break;
                case 2:
                    if (stream[i] == '\n' || stream[i] == '\r') {
                        status = 0;
                        break;
                    }
                    stream[i] = ' ';
                    break;
                case 3:
                    if (stream[i] == '*')
                        status = 4;
                    else if (stream[i] == '\n' || stream[i] == '\r')
                        break;
                    stream[i] = ' ';
                    break;
                case 4:
                    status = stream[i] == '/' ? 0 : 3;
                    if (stream[i] != '\n' && stream[i] != '\r')
                        stream[i] = ' ';
                    break;
            }
    }

    inline JSLbool extractFirstPath(const JSLstring &path, JSLstring &first, JSLstring &rest) {
    	JSLstringSize pos1 = path.find('.');
    	JSLstringSize pos2 = path.find('[');
    	if (pos1 < pos2 && pos1 != JSLstring::npos) {
    		first = path.substr(0, pos1);
    		rest = path.substr(pos1 + 1);
    		return true;
    	} else if (pos2 != JSLstring::npos) {
    		if (pos2 == 0) {
    			pos2 = path.find(']');
    			if (pos2 != JSLstring::npos) {
    				first = path.substr(1, pos2 - 1);
    				rest = path.substr(pos2 + (path[pos2 + 1] == '.' ? 2 : 1));
    				return true;
    			} else
    				return false;
    		} else {
    			first = path.substr(0, pos2);
				rest = path.substr(pos2);
				return true;
    		}
    	} else if (path != "") {
            first = path;
            rest = "";
            return true;
    	} else {
            first = "";
            rest = "";
            return true;
    	}
        return false;
    }

    inline JSLstringSize asStringLength() const {
    	JSLstringSize len = 0;
        switch (_type) {
            case NullType:
                return 4;
            case BoolType:
                return _data.asBool ? 4 : 5;
            case LongType:
                return boost::lexical_cast<JSLstring>(_data.asLong).length();
            case IntType:
                return boost::lexical_cast<JSLstring>(_data.asInt).length();
            case DoubleType:
                return boost::lexical_cast<JSLstring>(_data.asDouble).length();
            case FloatType:
                return boost::lexical_cast<JSLstring>(_data.asFloat).length();
            case StringType:
                return _data.asString->length() + 2;
            case ObjectType:
                for (JSLobjectIterator iter = _data.asObject->begin(); iter != _data.asObject->end(); iter++)
                    len += iter->first.length() + iter->second->asStringLength();
                len += _data.asObject->size() * 2 + 1;
                return len < 2 ? 2 : len;
            case ArrayType:
            	JSLarraySize size = _data.asArray->size();
                for (JSLarraySize i = 0; i < size; i++)
                    len += (*_data.asArray)[i]->asStringLength();
                len += size + 1;
                return len < 2 ? 2 : len;
        }
        return 0;
    }

    inline JSLstringSize calcBinarySize(JSLstringSize &structure, JSLstringSize &strings) const {
        structure += sizeof(JSLubyte);
        switch (_type) {
            case NullType:
                break;
            case BoolType:
                structure += sizeof(JSLbool);
                break;
            case LongType:
                structure += sizeof(JSLlong);
                break;
            case DoubleType:
                structure += sizeof(JSLdouble);
                break;
            case IntType:
                structure += sizeof(JSLint);
                break;
            case FloatType:
                structure += sizeof(JSLsingle);
                break;
            case StringType:
                structure += sizeof(JSLuint) + sizeof(JSLuint);
                strings += _data.asString->length();
                break;
            case ObjectType:
                structure += sizeof(JSLuint);
                for (JSLobjectIterator iter = _data.asObject->begin(); iter != _data.asObject->end(); iter++) {
                    structure += sizeof(JSLuint) + sizeof(JSLubyte);
                    strings += iter->first.length();
                    iter->second->calcBinarySize(structure, strings);
                }
                break;
            case ArrayType:
                structure += sizeof(JSLuint);
                JSLarraySize size = _data.asArray->size();
                for (unsigned int i = 0; i < size; i++)
                    (*_data.asArray)[i]->calcBinarySize(structure, strings);
                break;
        }
        return structure + strings;
    }

    inline JSLstringSize writeJslStream(JSLchar* stream, const JSLstringSize start, const JSLstringSize maxLength) const {
    	JSLstringSize size, index, l;
    	JSLstringSize pos = start;
        switch (_type) {
            case StringType:
                l = _data.asString->length();
                if (pos + l + 1 < maxLength) {
                    stream[pos] = DEFAULT_STRING_DELIMITER;
                    pos++;
                    _data.asString->copy(stream + pos, l);
                    pos += l;
                    stream[pos] = DEFAULT_STRING_DELIMITER;
                    pos++;
                    return pos - start;
                }
                break;
            case ObjectType:
                if (pos < maxLength) {
                    stream[pos] = '{';
                    pos++;
                    index = 1;
                    size = _data.asObject->size();
                    for (JSLobjectIterator iter = _data.asObject->begin(); iter != _data.asObject->end(); iter++, index++) {
                        l = iter->first.length();
                        if (pos + l - 1 < maxLength) {
                            iter->first.copy(stream + pos, l);
                            pos += l;
                            if (pos < maxLength) {
                                stream[pos] = ':';
                                pos++;
                                pos += iter->second->writeJslStream(stream, pos, maxLength);
                            }
                            if (pos < maxLength && index < size) {
                                stream[pos] = ',';
                                pos++;
                            }
                        }
                    }
                    if (pos < maxLength) {
                        stream[pos] = '}';
                        pos++;
                    }
                    return pos - start;
                }
                break;
            case ArrayType:
                if (pos < maxLength) {
                    stream[pos] = '[';
                    pos++;
                    size = _data.asArray->size();
                    for (JSLstringSize i = 0; i < size; i++) {
                        pos += (*_data.asArray)[i]->writeJslStream(stream, pos, maxLength);
                        if (pos < maxLength && i < size - 1) {
                            stream[pos] = ',';
                            pos++;
                        }
                    }
                    if (pos < maxLength) {
                        stream[pos] = ']';
                        pos++;
                    }
                    return pos - start;
                }
                break;
            default:
                JSLstring res = this->asString();
                JSLstringSize l = res.length();
                if (pos + l - 1 < maxLength) {
                    res.copy(stream + pos, l);
                    return l;
                }
                break;
        }
        return 0;
    }

    inline JSLbool writeBinaryStream(JSLchar* structure, JSLchar* strings, JSLstringSize &structurePos, JSLstringSize &stringsPos, JSLstringSize stringsOffset) {
    	JSLuint size;
    	JSLstringSize pos1 = structurePos;
    	JSLstringSize pos2 = stringsPos;
        setmem(JSLubyte, structure + pos1, _type);
        pos1 += 1;
        switch (_type) {
            case NullType:
                break;
            case BoolType:
                setmem(JSLbool, structure + pos1, _data.asBool);
                pos1 += 1;
                break;
            case LongType:
                setmem(JSLlong, structure + pos1, _data.asLong);
                pos1 += 8;
                break;
            case DoubleType:
                setmem(JSLdouble, structure + pos1, _data.asDouble);
                pos1 += 8;
                break;
            case IntType:
                setmem(JSLint, structure + pos1, _data.asInt);
                pos1 += 4;
                break;
            case FloatType:
                setmem(JSLsingle, structure + pos1, _data.asFloat);
                pos1 += 4;
                break;
            case StringType:
                size = (JSLint)_data.asString->length();
                setmem(JSLuint, structure + pos1, (JSLuint)(pos2 + stringsOffset));
                pos1 += 4;
                setmem(JSLuint, structure + pos1, size);
                pos1 += 4;
                _data.asString->copy(strings + pos2, size);
                pos2 += size;
                break;
            case ObjectType:
                size = (JSLint)_data.asObject->size();
                setmem(JSLuint, structure + pos1, size);
                pos1 += 4;
                for (JSLobjectIterator iter = _data.asObject->begin(); iter != _data.asObject->end(); iter++) {
                    size = (JSLint)iter->first.length();
                    setmem(JSLuint, structure + pos1, (JSLuint)(pos2 + stringsOffset));
                    pos1 += 4;
                    setmem(JSLchar, structure + pos1, (JSLchar)size);
                    pos1 += 1;
                    iter->first.copy(strings + pos2, size);
                    pos2 += size;
                    if (!iter->second->writeBinaryStream(structure, strings, pos1, pos2, stringsOffset))
                        return false;
                }
                break;
            case ArrayType:
                size = (JSLint)_data.asArray->size();
                setmem(JSLuint, structure + pos1, size);
                pos1 += 4;
                for (JSLuint i = 0; i < size; i++)
                    if (!(*_data.asArray)[i]->writeBinaryStream(structure, strings, pos1, pos2, stringsOffset))
                        return false;
                break;
        }
        structurePos = pos1;
        stringsPos = pos2;
        return true;
    }

    inline static Node* parseObject(JSLint &end, const JSLchar* stream, const JSLint start, const JSLint length) {
    	JSLint pos = start;
        Node* pairValue = NULL;
        JSLstring name;

        Node* result = new Node();
        result->_type = ObjectType;
        result->_data.asObject = new JSLobject();

        while ((pairValue = parsePair(name, pos, stream, pos, length)) != NULL) {
            result->_data.asObject->insert(std::pair<JSLstring, Node*>(name, pairValue));
            nextNonWhite(pos, stream, pos, length);
            if (pos == length || stream[pos] == '}')
                break;
            if (stream[pos] == ',')
                pos++;
        }

        end = pos;
        return result;
    }
    inline static Node* parseArray(JSLint &end, const JSLchar* stream, const JSLint start, const JSLint length) {
        int pos = start;
        Node* value = NULL;

        Node* result = new Node();
        result->_type = ArrayType;
        result->_data.asArray = new JSLarray();

        while ((value = parseValue(pos, stream, pos, length)) != NULL) {
            result->_data.asArray->push_back(value);
            nextNonWhite(pos, stream, pos, length);
            if (pos == length || stream[pos] == ']')
                break;
            if (stream[pos] == ',')
                pos++;
        }

        end = pos;
        return result;
    }
    inline static Node* parsePair(JSLstring &name, JSLint &end, const JSLchar* stream, const JSLint start, const JSLint length) {
    	JSLint pos;
        nextNonWhite(pos, stream, start, length);

        if (pos >= length) {
            end = start;
            return NULL;
        }

        if (stream[pos] == '_' || isalpha(stream[pos])) {
        	JSLint nameStart = pos;
            pos++;
            while (pos < length && (stream[pos] == '_' || isalnum(stream[pos])))
                pos++;
            JSLint nameEnd = pos;

            nextNonWhite(pos, stream, pos, length);
            if (pos >= length) {
                end = start;
                return NULL;
            }

            if (stream[pos] == ':') {
                pos++;
                Node* value = parseValue(pos, stream, pos, length);
                if (value == NULL) {
                    end = start;
                    return NULL;
                }
                end = pos;
                name = JSLstring(stream + nameStart, nameEnd - nameStart);
                return value;
            } else {
                end = start;
                return NULL;
            }
        } else {
            end = start;
            return NULL;
        }
    }
    inline static Node* parseValue(JSLint &end, const JSLchar* stream, const JSLint start, const JSLint length) {
    	JSLint pos;
        nextNonWhite(pos, stream, start, length);

        if (pos >= length) {
            end = start;
            return NULL;
        }

        if (stream[pos] == '{') { // we are in array reading object
            pos++;
            Node* object = parseObject(pos, stream, pos, length);
            if (object == NULL) {
                end = start;
                return NULL;
            }
            nextNonWhite(pos, stream, pos, length);
            if (pos >= length || stream[pos] != '}') {
                delete object;
                end = start;
                return NULL;
            }
            pos++;
            end = pos;
            return object;
        } else if (stream[pos] == '[') { // we are in array reading array
            pos++;
            Node* array = parseArray(pos, stream, pos, length);
            if (array == NULL) {
                end = start;
                return NULL;
            }
            nextNonWhite(pos, stream, pos, length);
            if (pos >= length || stream[pos] != ']') {
                delete array;
                end = start;
                return NULL;
            }
            pos++;
            end = pos;
            return array;
        } else if (stream[pos] == '"' || stream[pos] == '\'') { // we are in array reading string
            JSLstring* str = new JSLstring();
            if (parseString(*str, pos, stream, pos, length)) {
                Node* string = new Node();
                string->_type = StringType;
                string->_data.asString = str;
                end = pos;
                return string;
            } else {
                delete str;
                pos = start;
                return NULL;
            }
        } else if (isdigit(stream[pos]) || stream[pos] == '-') { // we are in array reading numeric
            Data value;
            JSLint type;
            if (parseNumeric(value, type, pos, stream, pos, length)) {
                Node* numeric = new Node();
                numeric->_type = type;
                numeric->_data = value;
                end = pos;
                return numeric;
            } else {
                pos = start;
                return NULL;
            }
        } else if (isalpha(stream[pos]) || stream[pos] == '_') { // we are in array/object reading keyword
            JSLstring iden;
            if (parseIdentifier(iden, pos, stream, pos, length)) {
                end = pos;
                if (iden == "null") {
                    Node* val = new Node();
                    val->_type = NullType;
                    return val;
                } else if (iden == "true") {
                    Node* val = new Node();
                    val->_type = BoolType;
                    val->_data.asBool = true;
                    return val;
                } else if (iden == "false") {
                    Node* val = new Node();
                    val->_type = BoolType;
                    val->_data.asBool = false;
                    return val;
                }
            }
            end = start;
            return NULL;
        }

        return NULL;
    }
    inline static JSLbool parseIdentifier(JSLstring &outStr, JSLint &end, const JSLchar* stream, const JSLint start, const JSLint length) {
        nextNonWhite(end, stream, start, length);
        JSLint pos = end;

        if ((isalpha(stream[end]) != 0 || stream[end] == '_') && end < length) {
            end++;
            while ((isalnum(stream[end]) != 0 || stream[end] == '_') && end < length)
                end++;

            outStr = JSLstring(stream + pos, end - pos);
            return true;
        }

        end = start;
        return false;
    }
    inline static JSLbool parseString(JSLstring &outStr, JSLint &end, const JSLchar* stream, const JSLint start, const JSLint length) {
        if (end < length && (stream[end] == '"' || stream[end] == '\'')) {
        	JSLint pos = end;
        	JSLchar ech = stream[end];
            end++;

            JSLint escc = 0;
            while (end < length) {
                if (stream[end] == ech)
                    break;
                if (stream[end] == '\\') {
                    end++;
                    escc++;
                }
                end++;
            }

            if (end >= length) {
                outStr = "";
                return false;
            }

            pos++;
            JSLint len = end - pos - escc;
            JSLint slen = end - pos;
            JSLint j = 0;
            outStr.resize(len);
            for (JSLint i = 0; i < slen; i++) {
                if (stream[pos + i] == '\\') {
                    i++;
                    outStr[j] = escapingChar(stream[pos + i]);
                } else {
                    outStr[j] = stream[pos + i];
                }
                j++;
            }

            end++;
            return true;
        }

        outStr = "";
        end = start;
        return false;
    }
    inline static JSLbool parseNumeric(Data &value, JSLint &type, JSLint &end, const JSLchar* stream, const JSLint start, const JSLint length) {
        nextNonWhite(end, stream, start, length);
        JSLint pos = end;
        if (stream[end] == '-')
            end++;

        if (end < length && isdigit(stream[end]) != 0) {
            end++;
            while (end < length && isdigit(stream[end]) != 0)
                end++;
            if (end < length && stream[end] == '.') {
                end++;
                if (end < length && isdigit(stream[end]) != 0) {
                    end++;
                    while (end < length && isdigit(stream[end]) != 0)
                        end++;
                    type = FloatType;
                } else {
                    end--;
                    type = IntType;
                }
            } else
                type = IntType;
            JSLint tmpend = end;
            if (end < length && (stream[end] == 'e' || stream[end] == 'E')) {
                end++;
                if (end < length && (stream[end] == '+' || stream[end] == '-')) {
                    end++;
                    if (end < length && isdigit(stream[end]) != 0) {
                        end++;
                        while (end < length && isdigit(stream[end]) != 0)
                            end++;
                    } else
                        end = tmpend;
                } else
                    end = tmpend;
            }
            JSLstring valStr(stream + pos, end - pos);
            if (type == IntType) {
            	JSLlong tmp = boost::lexical_cast<JSLlong>(valStr);
                if (tmp < -2147483647 || tmp > 2147483647) {
                    value.asLong = tmp;
                    type = LongType;
                } else
                    value.asInt = (int)tmp;
            } else {
            	JSLdouble tmp = boost::lexical_cast<JSLdouble>(valStr);
                if (valStr.length() - (end - tmpend) - (valStr[0] == '-' ? 1 : 0) > 7) {
                    value.asDouble = tmp;
                    type = DoubleType;
                } else
                    value.asFloat = (JSLsingle)tmp;
            }
            return true;
        } else {
            end = start;
            type = NullType;
            value.asLong = 0;
            return false;
        }
    }

    inline static JSLchar escapingChar(JSLchar ch) {
        switch (ch) {
            case 'n':
                return '\n';
            case 't':
                return '\t';
            case 'r':
                return '\r';
            case 'f':
                return '\f';
            case 'b':
                return '\b';
            case 'a':
                return '\a';
            default:
                return ch;
        }
    }
};

class JSLConvertible {
public:
    virtual bool loadJSL(const Node *node) = 0;
    virtual bool saveJSL(Node *node) = 0;

    virtual ~JSLConvertible() {}
};

#undef setmem
#undef getmem
#undef nextNonWhite

} // namespace jsl

#endif
